package helpers

import (
	"context"
	"io"
	"os"
	"strings"
	"time"

	// . "github.com/onsi/ginkgo/v2"
	f5_bigip "github.com/f5devcentral/f5-bigip-rest-go/bigip"
	"github.com/f5devcentral/f5-bigip-rest-go/utils"
	"github.com/fatih/color"
	. "github.com/onsi/gomega"

	"github.com/onsi/gomega/types"
	"gopkg.in/yaml.v3"
)

type SuiteConfig struct {
	KubeConfig string `yaml:"kubeConfig"`
	CISCConfig struct {
		CISCImage string   `yaml:"ciscImage"`
		Arguments []string `yaml:"arguments"`
		Endpoint  string   `yaml:"endpoint"`
	} `yaml:"ciscConfig"`
	GatewayConfig struct {
		Controller struct {
			Image     string   `yaml:"image"`
			Arguments []string `yaml:"arguments"`
			NodePort  int      `yaml:"nodePort"`
		} `yaml:"controller"`
		Webhook struct {
			Image     string   `yaml:"image"`
			Arguments []string `yaml:"arguments"`
			NodePort  int      `yaml:"nodePort"`
		} `yaml:"webhook"`
	} `yaml:"gatewayConfig"`
	BIGIPConfig struct {
		Username  string `yaml:"username"`
		Password  string `yaml:"password"`
		IPAddress string `yaml:"ipAddress"`
		Port      int    `yaml:"port"`
	} `yaml:"bigipConfig"`
	TestConfig struct {
		Timeout  int    `yaml:"timeout"`
		Delay    int    `yaml:"delay"`
		LogLevel string `yaml:"logLevel"`
	} `yaml:"testConfig"`
}

type TestSuite struct {
	Context     context.Context
	Slog        *utils.SLOG
	SuiteConfig *SuiteConfig
}

type CheckStruct struct {
	Checks []struct {
		Uri    string
		Status int
		Body   map[string]interface{}
	}
}

func NewTestSuite(sc *SuiteConfig) *TestSuite {
	slog := utils.NewLog().WithLevel(sc.TestConfig.LogLevel)
	return &TestSuite{
		Slog:        slog,
		Context:     context.WithValue(context.Background(), utils.CtxKey_Logger, slog),
		SuiteConfig: sc,
	}
}

func NewSuiteConfig() *SuiteConfig {
	sc := &SuiteConfig{}
	if err := sc.Load("./test-config.yaml"); err != nil {
		panic(err)
	}
	return sc
}

func (sc *SuiteConfig) Load(filepath string) error {
	if f, err := os.Open(filepath); err != nil {
		return err
	} else {
		b, err := io.ReadAll(f)
		if err != nil {
			return err
		}
		sc.BIGIPConfig.Username = "admin"
		sc.BIGIPConfig.Port = 443
		sc.TestConfig.Delay = 1
		sc.TestConfig.Timeout = 60
		if err := yaml.Unmarshal(b, sc); err != nil {
			return err
		}
	}

	return nil
}

func (ts *TestSuite) WaitAndAssert(f interface{}, args ...interface{}) types.AsyncAssertion {
	return Eventually(f).
		ProbeEvery(time.Duration(ts.SuiteConfig.TestConfig.Delay) * time.Second).
		WithContext(ts.Context).
		WithArguments(args...).
		WithTimeout(time.Duration(ts.SuiteConfig.TestConfig.Timeout) * time.Second)
}

func (ts *TestSuite) ForceDeleteIfExistsAfter(bip *BIGIPHelper, partitions []string, seconds int) {

	doDelete := func(pts []string, rootKinds []string) {
		results := map[string]map[string]interface{}{}
		ts.Slog.Infof("grabbing partition %s with kinds %s for deletion", pts, rootKinds)
		if err := bip.GrabPartition(pts, rootKinds, results); err != nil {
			ts.Slog.Warnf("failed to grab ltm and sys for %s: %s", pts, err.Error())
			return
		}

		for _, pt := range pts {
			orig := results[pt]
			kinds := f5_bigip.GatherKinds(&orig, nil)
			existings, err := bip.BC.GetExistingResources(pt, kinds)
			if err != nil {
				ts.Slog.Warnf("failed to get existing resources from %s: %s", pt, err.Error())
				return
			}
			cmds, err := bip.BC.GenRestRequests(pt, &orig, nil, existings)
			if err != nil {
				ts.Slog.Warnf("failed to generate rest requests for %s: %s", pt, err.Error())
				return
			}
			if err := bip.BC.DoRestRequests(cmds); err != nil {
				ts.Slog.Warnf("failed to do deletion for %s: %s", pt, err.Error())
				return
			}
			if pt == "cis-c-tenant" || pt == "Common" {
				return
			}
			if err := bip.BC.DeletePartition(pt); err != nil {
				ts.Slog.Warnf("cannot delete partition %s: %s", pt, err.Error())
				return
			}
		}
	}

	pts := []string{}
	for _, partition := range partitions {
		if bip.PartitionIsCreated(partition) {
			pts = append(pts, partition)
		}
	}
	doDelete(pts, []string{"ltm", "sys"})
	if len(pts) > 0 {
		doDelete([]string{"cis-c-tenant"}, []string{"net/arp"})
	}
}

func (ts *TestSuite) TestWithSystest(bip *BIGIPHelper, systest []byte) {
	var checks CheckStruct
	Expect(yaml.Unmarshal(systest, &checks)).To(Succeed())

	for _, check := range checks.Checks {
		u := check.Uri
		if strings.HasPrefix(u, "/mgmt/tm/") {
			u = strings.Replace(u, "/mgmt/tm/", "", 1)
		}
		if check.Status == 404 {
			ts.WaitAndAssert(bip.UrlNotFound, u).Should(Equal(true))
		} else {
			check.Status = 200
			ts.WaitAndAssert(bip.Contains, u, check.Body).Should(Equal(true))
		}
		ts.Slog.Infof("==> checked %d %s", check.Status, color.RedString(check.Uri))
	}
}

func (ts *TestSuite) TenantFromChecks(systest []byte) []string {

	ps := []string{}
	var checks CheckStruct
	Expect(yaml.Unmarshal(systest, &checks)).To(Succeed())
	for _, check := range checks.Checks {
		if p := TenantFromUri(check.Uri); p != "" {
			ps = append(ps, p)
		}
	}
	return ps
}
